home *** CD-ROM | disk | FTP | other *** search
/ PC Open 107 / PC Open 107 CD 1.bin / CD1 / INTERNET / COPIA SITI / Getleft / getleft-setup-notcl.exe / {app} / scripts / tkwizard.tcl < prev    next >
Encoding:
Text File  |  2004-02-21  |  35.7 KB  |  1,092 lines

  1. # Copyright (c) 2001, Bryan Oakley
  2. # All Rights Reservered
  3. #
  4. # Bryan Oakley
  5. # oakley_at_bardo.clearlight.com
  6. #
  7. # tkwizard 1.0a1
  8. #
  9. # this code is freely distributable without restriction, and is 
  10. # provided as-is with no warranty expressed or implied. 
  11. #
  12.  
  13. package require Tk 8.0
  14. package provide tkwizard 1.0
  15.  
  16. # create the package namespace, and do some basic initialization
  17. namespace eval tkwizard {
  18.  
  19.     namespace export tkwizard
  20.     
  21.     set ns [namespace current]
  22.  
  23.     # define class bindings
  24.     bind Wizard <<WizHelp>>     [list ${ns}::handleEvent %W <<WizHelp>>]
  25.     bind Wizard <<WizNextStep>> [list ${ns}::handleEvent %W <<WizNextStep>>]
  26.     bind Wizard <<WizPrevStep>> [list ${ns}::handleEvent %W <<WizPrevStep>>]
  27.     bind Wizard <<WizCancel>>   [list ${ns}::handleEvent %W <<WizCancel>>]
  28.     bind Wizard <<WizFinish>>   [list ${ns}::handleEvent %W <<WizFinish>>]
  29.  
  30.     # create a default image
  31.     image create photo [namespace current]::feather -data {
  32.        R0lGODlhIAAgALMAANnZ2QAAwAAA/wBAwAAAAICAgAAAgGBggKCgpMDAwP//
  33.        /////////////////////yH5BAEAAAAALAAAAAAgACAAAAT/EMhJq60hhHDv
  34.        pVCQYohAIJBzFgpKoSAEAYcUIRAI5JSFlkJBCGLAMYYIIRAI5ASFFiqDgENK
  35.        EUIwBAI5ywRlyhAEHFKKEEIgEMgJyiwUBAGHnCKEEAyBQM4yy5RhCDikFDBI
  36.        SSCQExRKwxBDjAGHgEFKQyCQk9YgxBBjDAGDnAQCOWkNQgwxxDgwyGkIBHJS
  37.        GoQQYohRYJDTEAjkpDWIIYQQBQY5A4FATlqDEEIMgWCQMxgCgZy0BiikRDDI
  38.        GQyBQE5aAxRSIhjkNIRAICetAQop04BBTgOBnLTKIIQQacAgZzAQyEkrCEII
  39.        kQYMckoDgZy0giCESAMGOaWBQMoydeeUQYhUYJBTGgikLHNOGYRACQY5pYFA
  40.        yjLnnEGgNGCQMxgAACgFAjnpFEUNGOQ0BgI5Z6FUFlVgkJNAICctlMqiyggB
  41.        BkMIBHLOUiidSUEiJwRyzlIopbJQSilFURJUIJCTVntlKhhjCwsEctJqr0wF
  42.        Y0xhBAA7
  43.     }
  44.  
  45.     # Make a class binding to do some housekeeping
  46.     bind Wizard <Destroy> [list ${ns}::wizard-destroy %W]
  47. }
  48.  
  49. # usage: tkwizard ?-showhelp boolean? ?-title string? 
  50. proc tkwizard::tkwizard {name args} {
  51.  
  52.     set showHelp 0
  53.     set body {}
  54.  
  55.     set i 0
  56.     while {$i < [llength $args]} {
  57.         set arg [lindex $args $i]
  58.         switch -glob -- $arg {
  59.             -showhelp {
  60.                 incr i
  61.                 set showHelp [lindex $args $i]
  62.             }
  63.             -title {
  64.                 incr i
  65.                 set title [lindex $args $i]
  66.             }
  67.             default {
  68.                 return -code error "unknown option \"$arg\" (!)"
  69.             }
  70.         }
  71.         incr i
  72.     }
  73.             
  74.     if {![info exists title]} {set title $name}
  75.  
  76.     init $name $showHelp $title
  77.  
  78.     return $name
  79. }
  80.  
  81. ##
  82. # wizard-destroy
  83. #
  84. # does cleanup of the wizard when it is destroyed. Specifically,
  85. # it destroys the associated namespace
  86. proc tkwizard::wizard-destroy {name} {
  87.  
  88.     upvar #0 [namespace current]::@$name-state wizState
  89.  
  90.     if {![info exists wizState]} {
  91.         return -code error "unknown wizard \"$name\""
  92.     }
  93.     set w $wizState(window)
  94.     interp alias {} $wizState(alias) {}
  95.     catch {namespace delete $wizState(namespace)} message
  96.  
  97.     return ""
  98. }
  99.  
  100.  
  101. # intended for an end user to draw a step for the purpose
  102. # of measuring it's size. Not fully realized yet; it seems to 
  103. # put the wizard in a slightly weird state
  104. proc tkwizard::wizProx-drawstep {name stepname} {
  105.  
  106.     upvar #0 [namespace current]::@$name-state wizState
  107.     upvar #0 [namespace current]::@$name-config wizConfig
  108.     upvar #0 [namespace current]::@$name-stepData wizStepData
  109.  
  110.     # First, build the appropriate layout...
  111.     set layout $wizStepData($stepname,layout)
  112.     buildLayout $name $layout
  113.  
  114.     # then build the step...
  115.     set wizConfig(-step) $stepname
  116.     buildStep $name $stepname
  117. }
  118.  
  119. ##
  120. proc tkwizard::wizProc-cget {name args} {
  121.     upvar #0 [namespace current]::@$name-config wizConfig
  122.  
  123.     if {[llength $args] != 1} {
  124.         return -code error "wrong \# args: should be \"$name cget option\""
  125.     }
  126.     set option [lindex $args 0]
  127.     if {[info exists wizConfig($option)]} {
  128.         return $wizConfig($option)
  129.     } 
  130.     return -code error "unknown option \"$option\""
  131. }
  132.  
  133. proc tkwizard::wizProc-configure {name args} {
  134.     upvar #0 [namespace current]::@$name-config wizConfig
  135.  
  136.     if {[llength $args] == 0} {
  137.         set result [list]
  138.         foreach item [lsort [array names wizConfig]] {
  139.             lappend result $item $wizConfig($item)
  140.         }
  141.         return $result
  142.  
  143.     } elseif {[llength $args] == 1} {
  144.         uplevel $name cget [lindex $args 0]
  145.  
  146.     } else {
  147.         foreach {option value} $args {
  148.             if {![info exists wizConfig($option)]} {
  149.                 return -code error "unknown option \"$option\""
  150.             }
  151.             set wizConfig($option) $value
  152.             switch -exact -- $option {
  153.                 -background {
  154.                     $wizConfig(toplevel) configure -background $value
  155.                     # in theory we should step through all widgets,
  156.                     # changing their color as well. Maybe I generate
  157.                     # a virtual event like <<WizConfigure>> so the
  158.                     # programmer can reconfigure their steps appropriately
  159.                 }
  160.                 -title {
  161.                     wm title $w $value
  162.                 }
  163.             }
  164.         }
  165.     }
  166. }
  167.  
  168. ##
  169. # wizProc
  170. #
  171. # this is the procedure that represents the wizard object; each
  172. # wizard will be aliased to this proc; the wizard name will be
  173. # provided as the first argument (this is transparent to the caller)
  174.  
  175. proc tkwizard::wizProc {name command args} {
  176.     # define the state variable here; that way the worker procs
  177.     # can do an uplevel to access the variable with a simple name
  178.     variable @$name-state
  179.  
  180.     # call the worker proc
  181.     eval wizProc-$command $name $args
  182. }
  183.  
  184. ##
  185. # wizProc-hide
  186. #
  187. # usage: wizHandle hide
  188. #
  189. # hides the wizard without destroying it. Note that state is NOT
  190. # guaranteed to be preserved, since a subsequent "show" will reset
  191. # the state. 
  192.  
  193. proc tkwizard::wizProc-hide {name args} {
  194.     upvar #0 [namespace current]::@$name-state wizState
  195.  
  196.     wm withdraw $wizState(window)
  197. }
  198.  
  199. ##
  200. # wizProc-order
  201. #
  202. # usage: wizHandle order ?-nocomplain? ?step step ...?
  203. #
  204. # example: wizHandle order step1 step2 step3 finalStep
  205. #
  206. # unless -nocomplain is specified, will throw an error if
  207. # a nonexistent step is given, or if a duplicate step is
  208. # given.
  209. #
  210. # without any steps, will return the current order
  211.  
  212. proc tkwizard::wizProc-order {name args} {
  213.     upvar #0 [namespace current]::@$name-state wizState
  214.  
  215.     set i [lsearch -exact $args "-nocomplain"]
  216.     set complain 1
  217.  
  218.     if {$i >= 0} {
  219.         set complain 0
  220.         set args [lreplace $args $i $i]
  221.     }
  222.  
  223.     if {$complain} {
  224.         # make sure all of the steps are defined.  "defined" means
  225.         # there is a initialize proc for that step. We also need to
  226.         # make sure we don't have the same step represented twice.
  227.         # This is inefficient, but speed isn't particularly critical
  228.         # here
  229.         array set found [list]
  230.         foreach step $args {
  231.             set tmp [info commands $wizState(namespace)::initialize-$step]
  232.             if {[llength $tmp] != 1} {
  233.                 return -code error "unknown step \"$step\""
  234.             }
  235.             if {[info exists found($step)]} {
  236.                 return -code error "duplicate step \"$step\""
  237.             }
  238.             set found($step) 1
  239.         }
  240.     }
  241.  
  242.     if {[llength $args] == 0} {
  243.         return $wizState(steps)
  244.     } else {
  245.         set wizState(steps) $args
  246.     }
  247. }
  248.  
  249. ##
  250. # wizProc-step
  251. #
  252. # implements the "step" method of the wizard object. The body
  253. # argument is code that will be run when the step identified by
  254. # 'stepName' is to be displayed in the wizard
  255. #
  256. # usage: wizHandle step stepName ?-layout layout? body
  257. #
  258.  
  259. proc tkwizard::wizProc-step {name stepName args} {
  260.  
  261.     upvar #0 [namespace current]::@$name-state wizState
  262.     upvar #0 [namespace current]::@$name-stepData wizStepData
  263.  
  264.     set body [lindex $args end]
  265.     set args [lreplace $args end end]
  266. #    set args [lrange $args 0 end-1]
  267.  
  268.     set layout "basic"
  269.     set i [lsearch -exact $args {-layout}]
  270.     if {$i >= 0} {
  271.         set j [expr {$i + 1}]
  272.         set layout [lindex $args $j]
  273.         if {[llength [info commands [namespace current]::buildLayout-$layout]] == 0} {
  274.             return -code error "unknown layout \"$layout\""
  275.         }
  276.         set args [lreplace $args $i $j]
  277.     }
  278.     set wizStepData($stepName,layout) $layout
  279.  
  280.     lappend wizState(steps) $stepName
  281.  
  282.     set procname "[namespace current]::${name}::initialize-$stepName"
  283.     proc $procname {} "[list set this $name];\n$body"
  284. }
  285.  
  286. ##
  287. # wizProc-widget
  288. #
  289. # Returns the path to an internal widget, or executes the
  290. # an internal widget command
  291. #
  292. # usage: wizHandle widget widgetName ?args?
  293. #
  294. # if [llength $args] > 0 it will run the widget command with
  295. # the args. Otherwise it will return the widget path
  296.  
  297. proc tkwizard::wizProc-widget {name args} {
  298.     upvar #0 [namespace current]::@$name-state wizState
  299.  
  300.     if {[llength $args] == 0} {
  301.         # return a list of all widget names
  302.         set result [list]
  303.         foreach item [array names wizState widget,*] {
  304.             regsub {widget,} $item {} item
  305.             lappend result $item
  306.         }
  307.         return $result
  308.     }
  309.  
  310.     set widgetname [lindex $args 0]
  311.     set args [lrange $args 1 end]
  312.  
  313.     if {![info exists wizState(widget,$widgetname)]} {
  314.         return -code error "unknown widget: \"$widgetname\""
  315.     }
  316.  
  317.     if {[llength $args] == 0} {
  318.         return $wizState(widget,$widgetname)
  319.     }
  320.  
  321.     # execute the widget command
  322.     eval [list $wizState(widget,$widgetname)] $args
  323. }
  324.  
  325. ##
  326. # wizProc-info
  327. #
  328. # Returns the information in the state array
  329. # usage: wizHandle info
  330.  
  331. proc tkwizard::wizProc-info {name args} {
  332.  
  333.     if {[llength $args] > 0} {
  334.         return -code error "wrong \# args: should be \"$name info\""
  335.     } 
  336.     upvar #0 [namespace current]::@$name-state wizState
  337.  
  338.     foreach item [lsort [array names wizState]] {
  339.         puts "$item = $wizState($item)"
  340.     }
  341. }
  342.  
  343. # return the namespace of the wizard
  344. proc tkwizard::wizProc-namespace {name} {
  345.     set ns [namespace current]::${name}
  346.     return $ns
  347. }
  348.  
  349. # execute the code in the namespace of the wizard
  350. proc tkwizard::wizProc-eval {name code} {
  351.     set ns [namespace current]::${name}
  352.     namespace eval $ns $code
  353. }
  354.     
  355. ##
  356. # wizProc-show
  357. # Causes the wizard to be displayed in it's initial state
  358. #
  359. # usage: wizHandle show
  360. #
  361. # This is where all of the widgets are created, though eventually
  362. # I'll probably move the widget drawing to a utility proc...
  363.  
  364. proc tkwizard::wizProc-show {name args} {
  365.  
  366.     upvar #0 [namespace current]::@$name-state wizState
  367.     upvar #0 [namespace current]::@$name-config wizConfig
  368.  
  369.     # initialize the remainder of the wizard state
  370.     set wizState(history)         [list]
  371.     set wizConfig(-previousstep)  ""
  372.     set wizConfig(-nextstep)      ""
  373.  
  374.     set steps $wizState(steps)
  375.     if {[llength $steps] == 0} {
  376.         # no steps? Just show it as-is.
  377.         wm deiconify $name
  378.         return
  379.     }
  380.  
  381.     # set a trace on where we store the next state. The trace
  382.     # will cause the next and previous buttons to become
  383.     # enabled or disabled. Thus, within a step a programmer can
  384.     # decide when to enable or disable the buttons by merely 
  385.     # setting these variables.
  386.     set code [namespace code "varTrace [list $name]"]
  387.     set stateVar "[namespace current]::@$name-config"
  388.     foreach item {-previousstep -nextstep -state -complete} {
  389.         trace vdelete  ${stateVar}($item) wu $code
  390.         trace variable ${stateVar}($item) wu $code
  391.     }
  392.  
  393.     # show the first step
  394.     set wizState(history) [lindex $steps 0]
  395.     showStep $name 
  396.  
  397.     # make it so, Number One
  398.     update idletasks
  399.     wm deiconify $wizState(window)
  400.  
  401.     # This makes sure closing the window with the window manager control
  402.     # Does The Right Thing (now if only I could figure out what the
  403.     # Right Thing is...)
  404.     wm protocol $name WM_DELETE_WINDOW \
  405.         [list $wizState(widget,cancelButton) invoke]
  406.  
  407.     return ""
  408. }
  409.  
  410. # This gets called whenever certain parts of our state variable
  411. # get set or unset (presently this only happens with -nextstep 
  412. # and -previousstep)
  413. proc tkwizard::varTrace {name varname index op} {
  414.     upvar #0 [namespace current]::@$name-state wizState
  415.     upvar #0 [namespace current]::@$name-config wizConfig
  416.  
  417.     catch {
  418.         switch -- $index {
  419.             -state {
  420.                 set state $wizConfig(-state)
  421.                 if {[string equal $state "normal"]} {
  422. #                    $name configure -cursor {}
  423.                     if {[string length $wizConfig(-previousstep)] == 0} {
  424.                         $wizState(widget,backButton) configure -state disabled
  425.                     } else {
  426.                         $wizState(widget,backButton) configure -state normal
  427.                     }
  428.                     if {[string length $wizConfig(-nextstep)] == 0} {
  429.                         $wizState(widget,nextButton) configure -state disabled
  430.                     } else {
  431.                         $wizState(widget,nextButton) configure -state normal
  432.                     }
  433.                     if {$wizConfig(-complete)} {
  434.                         $wizState(widget,finishButton) configure -state normal
  435.                     } else {
  436.                         $wizState(widget,finishButton) configure -state disabled
  437.                     }
  438.  
  439.                 } else {
  440.  #                    $name configure -cursor watch
  441.                     $wizState(widget,cancelButton) configure -cursor left_ptr
  442.                     $wizState(widget,nextButton)   configure -state disabled
  443.                     $wizState(widget,backButton)   configure -state disabled
  444.                     $wizState(widget,helpButton)   configure -state disabled
  445.                     $wizState(widget,finishButton) configure -state disabled
  446.                 }
  447.             }
  448.             -complete {
  449.                 if {$wizConfig(-complete)} {
  450.                     $wizState(widget,finishButton) configure -state normal
  451.                 } else {
  452.                     $wizState(widget,finishButton) configure -state disabled
  453.                 }
  454.             }
  455.  
  456.             -previousstep {
  457.                 set state normal
  458.                 if {[string length $wizConfig(-previousstep)] == 0} {
  459.                     set state disabled
  460.                 }
  461.                 $wizState(widget,backButton) configure -state $state
  462.             }
  463.             -nextstep {
  464.                 set state normal
  465.                 if {[string length $wizConfig(-nextstep)] == 0} {
  466.                     set state disabled
  467.                 }
  468.                 $wizState(widget,nextButton) configure -state $state
  469.             }
  470.  
  471.             default {
  472.                 puts "bogus variable trace: name=$varname index=$index op=$op"
  473.             }
  474.         }
  475.     }
  476. }
  477.  
  478. # Causes a step to be built by clearing out the current contents of
  479. # the client window and then executing the initialization code for
  480. # the given step
  481.  
  482. proc tkwizard::buildStep {name step}  {
  483.     upvar #0 [namespace current]::@$name-state wizState
  484.  
  485.     # reset the state of the windows in the wizard
  486.     eval destroy [winfo children $wizState(widget,clientArea)]
  487. #    wizProc-stepconfigure $name -title "" -subtitle "" -pretext "" -posttext ""
  488.  
  489.     namespace eval $wizState(namespace) initialize-$step 
  490.  
  491. }
  492.  
  493.  
  494. # This block of code is common to all wizard actions. 
  495. # (ie: it is the target of the -command option for wizard buttons)
  496. proc tkwizard::cmd {command name} {
  497.  
  498.     upvar #0 [namespace current]::@$name-state wizState
  499.     upvar #0 [namespace current]::@$name-config wizConfig
  500.  
  501.     switch $command {
  502.         Help       {event generate $name <<WizHelp>>}
  503.         Next       {event generate $name <<WizNextStep>>}
  504.         Previous   {event generate $name <<WizPrevStep>>}
  505.         Finish     {event generate $name <<WizFinish>>}
  506.         Cancel     {event generate $name <<WizCancel>>}
  507.  
  508.         default {
  509.             puts "'$command' not implemented yet"
  510.         }
  511.     }
  512. }
  513.  
  514. proc tkwizard::handleEvent {name event} {
  515.  
  516.     upvar #0 [namespace current]::@$name-state wizState
  517.     upvar #0 [namespace current]::@$name-config wizConfig
  518.     upvar #0 [namespace current]::@$name-stepData wizStepData
  519.  
  520.     switch $event {
  521.         <<WizHelp>> {
  522.             # not implemented yet
  523.         }
  524.  
  525.         <<WizNextStep>> {
  526.             set thisStep [lindex $wizState(history) end]
  527.             lappend wizState(history) $wizConfig(-nextstep)
  528.             showStep $name 
  529.         }
  530.  
  531.         <<WizPrevStep>> {
  532.  
  533.             # pop an item off of the history
  534.             set p [expr {[llength $wizState(history)] -2}]
  535.             set wizState(history) [lrange $wizState(history) 0 $p]
  536.             showStep $name 
  537.         }
  538.  
  539.         <<WizFinish>> {
  540.  
  541.             set thisStep [lindex $wizState(history) end]
  542.             wizProc-hide $name
  543.         }
  544.  
  545.         <<WizCancel>> {
  546.  
  547.             wizProc-hide $name
  548.         }
  549.  
  550.         default {
  551.             puts "'$event' not implemented yet"
  552.         }
  553.     }
  554. }
  555.  
  556. proc tkwizard::showStep {name} {
  557.  
  558.     upvar #0 [namespace current]::@$name-state wizState
  559.     upvar #0 [namespace current]::@$name-config wizConfig
  560.     upvar #0 [namespace current]::@$name-stepData wizStepData
  561.  
  562.     # the step is whatever is at the tail end of our 
  563.     # history
  564.     set step [lindex $wizState(history) end]
  565.     set proc "initialize-$step"
  566.     set wizConfig(-step) $step
  567.  
  568.     set layout $wizStepData($step,layout)
  569.  
  570.     # First, build the appropriate layout...
  571.     buildLayout $name $layout
  572.  
  573.     # then build the step...
  574.     set steps $wizState(steps)
  575.     set lastStep [expr {[llength $steps] -1}]
  576.     set stepIndex [lsearch $steps $step]
  577.     set prevIndex [expr {$stepIndex -1}]
  578.     set nextIndex [expr {$stepIndex + 1}]
  579.  
  580.     # initialize the next, previous and current step configuration
  581.     # options; this will set the state of the next/previous buttons.
  582.     # note that the user can retrieve these values with the normal
  583.     # 'cget' and 'configure' methods
  584.     set p [expr {[llength $wizState(history)] -2}]
  585.     set wizConfig(-previousstep) [lindex $wizState(history) $p]
  586.     set wizConfig(-nextstep) [lindex $steps $nextIndex]
  587.  
  588.     if {$stepIndex == ([llength $steps]-1)} {
  589.         set wizConfig(-complete) 1
  590.     } else {
  591.         set wizConfig(-complete) 0
  592.     }
  593.  
  594.     buildStep $name $step
  595.  
  596. }
  597.  
  598.  
  599. proc tkwizard::init {name showHelp title} {
  600.  
  601.     # name should be a widget path
  602.     set w $name
  603.  
  604.     # create variables in this namespace to keep track
  605.     # of the state of this wizard. We do this here to 
  606.     # avoid polluting the namespace of the widget. We'll
  607.     # create local aliases for the variables to make the
  608.     # code easier to read and write
  609.  
  610.     # this variable contains state information about the 
  611.     # wizard, such as the wizard title, the name of the 
  612.     # window and namespace associated with the wizard, the
  613.     # list of steps, and so on.
  614.     variable "@$name-state"
  615.     upvar \#0 [namespace current]::@$name-state wizState
  616.  
  617.     # this variable contains all of the parameters associated
  618.     # with the wizard and settable with the "configure" method
  619.     variable "@name-config"
  620.     upvar \#0 [namespace current]::@$name-config wizConfig
  621.  
  622.     # this is an experimental array containing data of known
  623.     # step types. Presently not being used.
  624.     variable "@name-stepTypes"
  625.     upvar \#0 [namespace current]::@$name-stepTypes wizStepTypes
  626.  
  627.     # this contains step-specific data, such as the step title
  628.     # and subtitle, icon, etc. All elements are unset prior to
  629.     # rendering a given step. It is each step's responsibility
  630.     # to set it appropriately, and it is each step type's 
  631.     # responsibility to use the data.
  632.     variable "@name-stepData"
  633.     upvar \#0 [namespace current]::@$name-stepData  wizStepData
  634.  
  635.     # do some state initialization; more will come later when
  636.     # the wizard is actually built
  637.     set wizConfig(-complete)      0
  638.     set wizConfig(-state)         normal
  639.     set wizConfig(-title)         $title
  640.     set wizConfig(-nextstep)      ""
  641.     set wizConfig(-previousstep)  ""
  642.     set wizConfig(-step)          ""
  643.     set wizConfig(-showhelp)      $showHelp
  644.  
  645.     set wizState(title)        $title
  646.     set wizState(window)       $w
  647.     set wizState(steps)        [list]
  648.     set wizState(namespace)    [namespace current]::$name
  649.     set wizState(name)         $name
  650.     set wizState(toplevel)     {}
  651.  
  652.     # create the wizard (except for the step pages...)
  653.     buildDialog $name
  654.  
  655.     # this establishes a namespace for this wizard; this namespace
  656.     # will contain wizard-specific data managed by the creator of
  657.     # the wizard
  658.     namespace eval $name {}
  659.  
  660.     # this creates the instance command by first renaming the widget
  661.     # command associated with our toplevel, then making an alias 
  662.     # to our own command
  663.     set wizState(toplevel) $wizState(namespace)::originalWidgetCommand
  664.     rename $w $wizState(toplevel)
  665.     interp alias {} ::$w {} [namespace current]::wizProc $name
  666.     set wizState(alias) ::$w
  667.  
  668.     # set some useful configuration values
  669.     set wizConfig(-background) \
  670.         [$wizState(namespace)::originalWidgetCommand cget -background]
  671. }
  672.  
  673. proc tkwizard::buildDialog {name} {
  674.     # This is custom for Getleft
  675.     global labelButtons
  676.  
  677.     upvar #0 [namespace current]::@$name-state wizState
  678.     upvar #0 [namespace current]::@$name-config wizConfig
  679.  
  680.     set prefix [string trimright $wizState(window) .]
  681.  
  682.     set wizState(widget,topframe)     $prefix.topframe
  683.     set wizState(widget,sep1)         $prefix.sep1
  684.     set wizState(widget,sep2)         $prefix.sep2
  685.     set wizState(widget,buttonFrame)  $prefix.buttonFrame
  686.     set wizState(widget,helpButton)   $prefix.buttonFrame.helpButton
  687.     set wizState(widget,nextButton)   $prefix.buttonFrame.nextButton
  688.     set wizState(widget,backButton)   $prefix.buttonFrame.backButton
  689.     set wizState(widget,cancelButton) $prefix.buttonFrame.cancelButton
  690.     set wizState(widget,finishButton) $prefix.buttonFrame.finishButton
  691.     set wizState(widget,layoutFrame)  $prefix.layoutFrame
  692.  
  693.     # create the toplevel window
  694.     set w $wizState(window)
  695.     toplevel $w -class Wizard -bd 2 -relief groove
  696.     wm title $w $wizConfig(-title)
  697.     wm withdraw $w
  698.     wm resizable $w 0 0
  699.  
  700.     # the dialog is composed of two areas: the row of buttons and the
  701.     # area with the dynamic content. To make it look the way we want it to
  702.     # we'll use another frame for a visual separator
  703.     frame $wizState(widget,buttonFrame) -bd 0 
  704.     frame $wizState(widget,layoutFrame) -bd 0
  705.     frame $wizState(widget,sep1) -class WizSeparator -height 2 -bd 2 -relief groove
  706.  
  707.     pack $wizState(widget,buttonFrame) -side bottom -fill x
  708.     pack $wizState(widget,sep1)  -side bottom -fill x
  709.     pack $wizState(widget,layoutFrame) -side top -fill both -expand y
  710.  
  711.     # make all of the buttons
  712.     button $wizState(widget,helpButton) \
  713.         -text "What's This?" \
  714.         -default normal \
  715.         -bd 1 \
  716.         -relief raised \
  717.         -command [namespace code "cmd Help [list $name]"]
  718.  
  719.     button $wizState(widget,backButton) \
  720.         -textvariable labelButtons(back) \
  721.         -default normal \
  722.         -width 9 \
  723.         -bd 1 \
  724.         -relief raised \
  725.         -command [namespace code "cmd Previous [list $name]"]
  726.  
  727.     button $wizState(widget,nextButton) \
  728.         -textvariable labelButtons(next) \
  729.         -default normal \
  730.         -width 9 \
  731.         -bd 1 \
  732.         -relief raised \
  733.         -command [namespace code "cmd Next [list $name]"]
  734.  
  735.     button $wizState(widget,finishButton) \
  736.         -textvariable labelButtons(finish) \
  737.         -default normal \
  738.         -width 9 \
  739.         -bd 1 \
  740.         -relief raised \
  741.         -command [namespace code "cmd Finish [list $name]"]
  742.  
  743.     button $wizState(widget,cancelButton) \
  744.         -textvariable labelButtons(cancel)   \
  745.         -default normal \
  746.         -width 9 \
  747.         -bd 1  \
  748.         -relief raised \
  749.         -command [namespace code "cmd Cancel [list $name]"]
  750.  
  751.     # pack the buttons
  752.     if {$wizConfig(-showhelp)} {
  753.         pack $wizState(widget,helpButton) -side left -padx 4 -pady 8
  754.     }
  755.     pack $wizState(widget,cancelButton) -side right -padx 4 -pady 8
  756.     pack $wizState(widget,finishButton) -side right -pady 8 -padx 4
  757.     pack $wizState(widget,nextButton) -side right -pady 8
  758.     pack $wizState(widget,backButton) -side right -pady 8
  759.  
  760.     # return the name of the toplevel, for lack of a better idea...
  761.     return $wizState(window)
  762. }
  763.  
  764. proc tkwizard::buildLayout {name layoutName} {
  765.     upvar #0 [namespace current]::@$name-state wizState
  766.     upvar #0 [namespace current]::@$name-config wizConfig
  767.  
  768.     set w $wizState(window)
  769.     set lf $wizState(widget,layoutFrame)
  770.  
  771.     # initialize the layout variables
  772.     initLayout-$layoutName $name
  773.  
  774.     # if the layout hasn't actually been built yet, build it
  775.     if {![winfo exists $lf.$layoutName]} {
  776.         buildLayout-$layoutName $name
  777.     }
  778.     eval pack forget [winfo children $lf]
  779.     pack $lf.$layoutName -side top -fill both -expand y
  780.  
  781. }
  782.  
  783. # this is a user-callable interface to configureLayout-<layout>
  784. proc tkwizard::wizProc-stepconfigure {name args} {
  785.     upvar #0 [namespace current]::@$name-state wizState
  786.     upvar #0 [namespace current]::@$name-config wizConfig
  787.     upvar #0 [namespace current]::@$name-stepData wizStepData
  788.  
  789.     set step $wizConfig(-step)
  790.     set layout $wizStepData($step,layout)
  791.     eval configureLayout-$layout $name $args
  792. }
  793.  
  794.  
  795. # this defines the widget paths. Will be called each time we
  796. # switch layouts
  797. proc tkwizard::initLayout-basic {name} {
  798.     upvar #0 [namespace current]::@$name-state wizState
  799.     upvar #0 [namespace current]::@$name-config wizConfig
  800.  
  801.     set layout $wizState(widget,layoutFrame).basic
  802.  
  803.     set wizState(widget,clientArea)   $layout.clientArea
  804.     set wizState(widget,icon)         $layout.icon
  805.     set wizState(widget,title)        $layout.title
  806.     set wizState(widget,subtitle)     $layout.subtitle
  807.     set wizState(widget,pretext)      $layout.pretext
  808.     set wizState(widget,posttext)     $layout.posttext
  809.  
  810. }
  811.  
  812. proc tkwizard::buildLayout-basic {name} {
  813.     upvar #0 [namespace current]::@$name-state wizState
  814.     upvar #0 [namespace current]::@$name-config wizConfig
  815.  
  816.     set layout $wizState(widget,layoutFrame).basic
  817.     frame $layout -class WizLayoutBasic
  818.  
  819.     # using the option database saves me from hard-coding it for
  820.     # every widget. I guess I'm just lazy.
  821.     option add *WizLayoutBasic*Label.justify             left interactive
  822.     option add *WizLayoutBasic*Label.anchor              nw   interactive
  823.     option add *WizLayoutBasic*Label.highlightThickness  0    interactive
  824.     option add *WizLayoutBasic*Label.borderWidth         0    interactive
  825.     option add *WizLayoutBasic*Label.padX                5    interactive
  826.  
  827.     # Client area. This is where the caller places its widgets.
  828.     frame $wizState(widget,clientArea) -bd 8 -relief flat
  829.     frame $layout.sep1 -class WizSeparator -height 2 -bd 2 -relief groove
  830.  
  831.     # title and subtitle and icon
  832.     frame $layout.titleframe -bd 4 -relief flat -background white
  833.     label $wizState(widget,title) -background white -width 40
  834.     label $wizState(widget,subtitle) -height 2 -background white -padx 15   -width 40
  835.     label $wizState(widget,icon) \
  836.         -borderwidth 0 \
  837.         -image [namespace current]::feather \
  838.         -background white \
  839.         -anchor c
  840.     set labelfont [font actual [$wizState(widget,title) cget -font]]
  841.     $wizState(widget,title) configure -font [concat $labelfont -weight bold]
  842.  
  843.     # put the title, subtitle and icon inside the frame we've
  844.     # built for them
  845.     set tf $layout.titleframe
  846.     grid $wizState(widget,title)    -in $tf -row 0 -column 0 -sticky nsew
  847.     grid $wizState(widget,subtitle) -in $tf -row 1 -column 0 -sticky nsew
  848.     grid $wizState(widget,icon)     -in $tf -row 0 -column 1 -rowspan 2 -padx 8
  849.     grid columnconfigure $tf 0 -weight 1
  850.     grid columnconfigure $tf 1 -weight 0
  851.  
  852.     # pre and post text. We'll pick rough estimates on the size of these
  853.     # areas. I noticed that if I didn't give it a width and height and a
  854.     # step defined a really, really long string, the label would try to
  855.     # accomodate the longest string possible, making the widget unnaturally
  856.     # wide.
  857.  
  858.     label $wizState(widget,pretext)  -width 40 
  859.     label $wizState(widget,posttext) -width 40
  860.  
  861.     # when our label widgets change size we want to reset the
  862.     # wraplength to that same size.
  863.     foreach widget {title subtitle pretext posttext} {
  864.         bind $wizState(widget,$widget) <Configure> {
  865.             # yeah, I know this looks weird having two after idle's, but
  866.             # it helps prevent the geometry manager getting into a tight
  867.             # loop under certain circumstances
  868.             #
  869.             # note that subtracting 10 is just a somewhat arbitrary number
  870.             # to provide a little padding...
  871.             after idle {after idle {%W configure -wraplength [expr {%w -10}]}}
  872.         }
  873.     }
  874.  
  875.     grid $layout.titleframe            -row 0 -column 0 -sticky nsew -padx 0
  876. #    grid $wizState(widget,title)      -row 0 -column 0 -sticky nsew -padx 0
  877. #    grid $wizState(widget,subtitle)   -row 1 -column 0 -sticky nsew -padx 0
  878. #    grid $wizState(widget,icon)       -row 0 -column 1 -rowspan 2 -sticky nsew -ipadx 10 -ipady 4
  879.     grid $layout.sep1                 -row 1 -sticky ew 
  880.     grid $wizState(widget,pretext)    -row 2 -sticky nsew -pady 8 -padx 8
  881.     grid $wizState(widget,clientArea) -row 3 -sticky nsew -padx 8 -pady 8
  882.     grid $wizState(widget,posttext)   -row 4 -sticky nsew -pady 8 -pady 8
  883.  
  884.     grid columnconfigure $layout 0 -weight 1
  885.     grid rowconfigure $layout 0 -weight 0
  886.     grid rowconfigure $layout 1 -weight 0
  887.     grid rowconfigure $layout 2 -weight 0
  888.     grid rowconfigure $layout 3 -weight 1
  889.     grid rowconfigure $layout 4 -weight 0
  890.  
  891.     # the pre and post text will initially not be visible. They will pop into
  892.     # existence if they are configured to have a value
  893.     grid remove $wizState(widget,pretext) $wizState(widget,posttext)
  894.  
  895. }
  896.  
  897. # usage: configureLayout-basic ?-title string? ?-subtitle string? ?-icon image?
  898. proc tkwizard::configureLayout-basic {name args} {
  899.     upvar #0 [namespace current]::@$name-state wizState
  900.     upvar #0 [namespace current]::@$name-config wizConfig
  901.  
  902.     if {[llength $args]%2 == 1} {
  903.         return -code error "wrong number of args..."
  904.     }
  905.  
  906.     foreach {option value} $args {
  907.         switch -- $option {
  908.             -title {
  909.                 $wizState(widget,title) configure -text "$value"
  910.             }
  911.  
  912.             -subtitle {
  913.                 $wizState(widget,subtitle) configure -text $value
  914.             }
  915.  
  916.             -icon {
  917.                 $wizState(widget,icon) configure -image $value
  918.             }
  919.  
  920.             -pretext {
  921.                 $wizState(widget,pretext) configure -text $value
  922.                 if {[string length $value] > 0} {
  923.                     grid $wizState(widget,pretext)
  924.  
  925.                 } else {
  926.                     grid remove $wizState(widget,pretext)
  927.                 }
  928.             }
  929.  
  930.             -posttext {
  931.                 $wizState(widget,posttext) configure -text $value
  932.                 if {[string length $value] > 0} {
  933.                     grid $wizState(widget,posttext)
  934.                 } else {
  935.                     grid remove $wizState(widget,posttext)
  936.                 }
  937.             }
  938.  
  939.             default {
  940.                 return -code error "unknown option \"$option\""
  941.             }
  942.         }
  943.     }
  944. }
  945.  
  946. ######
  947. # "Advanced" layout. Nothing really advanced about it, but that's what
  948. # microsoft seems to call wizards that look like this. Go figure.
  949. ######
  950.  
  951. proc tkwizard::initLayout-advanced {name} {
  952.     upvar #0 [namespace current]::@$name-state wizState
  953.     upvar #0 [namespace current]::@$name-config wizConfig
  954.  
  955.     set layout $wizState(widget,layoutFrame).advanced
  956.  
  957.     set wizState(widget,clientArea)   $layout.clientArea
  958.     set wizState(widget,icon)         $layout.icon
  959.     set wizState(widget,title)        $layout.title
  960.     set wizState(widget,subtitle)     $layout.subtitle
  961.     set wizState(widget,pretext)      $layout.pretext
  962.     set wizState(widget,posttext)     $layout.posttext
  963. }
  964.  
  965. proc tkwizard::buildLayout-advanced {name} {
  966.     upvar #0 [namespace current]::@$name-state wizState
  967.     upvar #0 [namespace current]::@$name-config wizConfig
  968.  
  969.     set layout $wizState(widget,layoutFrame).advanced
  970.  
  971.     frame $layout -class WizLayoutAdvanced
  972.  
  973.     # using the option database saves me from hard-coding it for
  974.     # every widget. I guess I'm just lazy.
  975.     option add *WizLayoutAdvanced*Label.justify             left interactive
  976.     option add *WizLayoutAdvanced*Label.anchor              nw   interactive
  977.     option add *WizLayoutAdvanced*Label.highlightThickness  0    interactive
  978.     option add *WizLayoutAdvanced*Label.borderWidth         0    interactive
  979.     option add *WizLayoutAdvanced*Label.padX                5    interactive
  980.  
  981.     # Client area. This is where the caller places its widgets.
  982.     frame $wizState(widget,clientArea) -bd 8 -relief flat
  983.     frame $layout.sep1 -class WizSeparator -height 2 -bd 2 -relief groove
  984.  
  985.     # title and subtitle
  986.     label $wizState(widget,title)
  987.     label $wizState(widget,subtitle) -height 2
  988.     array set labelfont [font actual [$wizState(widget,title) cget -font]]
  989.     set labelfont(-weight) bold
  990.     incr labelfont(-size) 6
  991.     $wizState(widget,title) configure -font [array get labelfont]
  992.  
  993.     # pre and post text. 
  994.     label $wizState(widget,pretext)
  995.     label $wizState(widget,posttext)
  996.  
  997.     # when our label widgets change size we want to reset the
  998.     # wraplength to that same size.
  999.     foreach widget {title subtitle pretext posttext} {
  1000.         bind $wizState(widget,$widget) <Configure> {
  1001.             # yeah, I know this looks weird having two after idle's, but
  1002.             # it helps prevent the geometry manager getting into a tight
  1003.             # loop under certain circumstances
  1004.             #
  1005.             # note that subtracting 10 is just a somewhat arbitrary number
  1006.             # to provide a little padding...
  1007.             after idle {after idle {%W configure -wraplength [expr {%w -10}]}}
  1008.         }
  1009.     }
  1010.  
  1011.     # icon
  1012.     label $wizState(widget,icon) \
  1013.         -borderwidth 1 \
  1014.         -relief sunken \
  1015.         -image [namespace current]::feather \
  1016.         -background white \
  1017.         -anchor c \
  1018.         -width 96
  1019.  
  1020.     grid $wizState(widget,icon)       -row 0 -column 0 -rowspan 5 -sticky nsew -pady 8 -padx 8
  1021.     grid $wizState(widget,title)      -row 0 -column 1 -sticky ew -pady 8  -padx 8
  1022.     grid $wizState(widget,subtitle)   -row 1 -column 1 -sticky ew -pady 8 -padx 8
  1023.     grid $wizState(widget,pretext)    -row 2 -column 1 -sticky ew -padx 8
  1024.     grid $wizState(widget,clientArea) -row 3 -column 1 -sticky nsew -padx 8
  1025.     grid $wizState(widget,posttext)   -row 4 -column 1 -sticky ew -padx 8 -pady 24
  1026.  
  1027.     grid columnconfigure $layout 0 -weight 0
  1028.     grid columnconfigure $layout 1 -weight 1
  1029.  
  1030.     grid rowconfigure $layout 0 -weight 0
  1031.     grid rowconfigure $layout 1 -weight 0
  1032.     grid rowconfigure $layout 2 -weight 0
  1033.     grid rowconfigure $layout 3 -weight 1
  1034.     grid rowconfigure $layout 4 -weight 0
  1035.  
  1036.     # the pre and post text will initially not be visible. They will pop into
  1037.     # existence if they are configured to have a value
  1038.     grid remove $wizState(widget,pretext) $wizState(widget,posttext)
  1039. }
  1040.  
  1041. proc tkwizard::configureLayout-advanced {name args} {
  1042.     upvar #0 [namespace current]::@$name-state wizState
  1043.     upvar #0 [namespace current]::@$name-config wizConfig
  1044.  
  1045.     if {[llength $args]%2 == 1} {
  1046.         return -code error "wrong number of args..."
  1047.     }
  1048.  
  1049.     foreach {option value} $args {
  1050.         switch -- $option {
  1051.             -title {
  1052.                 $wizState(widget,title) configure -text $value
  1053.             }
  1054.  
  1055.             -subtitle {
  1056.                 $wizState(widget,subtitle) configure -text $value
  1057.             }
  1058.  
  1059.             -icon {
  1060.                 $wizState(widget,icon) configure -image $value
  1061.             }
  1062.  
  1063.             -pretext {
  1064.                 $wizState(widget,pretext) configure -text $value
  1065.                 if {[string length $value] > 0} {
  1066.                     grid $wizState(widget,pretext)
  1067.                 } else {
  1068.                     grid remove $wizState(widget,pretext)
  1069.                 }
  1070.             }
  1071.  
  1072.             -posttext {
  1073.                 $wizState(widget,posttext) configure -text $value
  1074.                 if {[string length $value] > 0} {
  1075.                     grid $wizState(widget,posttext)
  1076.                 } else {
  1077.                     grid remove $wizState(widget,posttext)
  1078.                 }
  1079.             }
  1080.  
  1081.             default {
  1082.                 return -code error "unknown option \"$option\""
  1083.             }
  1084.         }
  1085.     }
  1086. }
  1087.  
  1088.